package com.xuan.judge.service.impl;


import cn.hutool.core.date.StopWatch;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ArrayUtil;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
import com.github.dockerjava.api.command.StatsCmd;
import com.xuan.common.core.constants.Constants;
import com.xuan.common.core.constants.JudgeConstants;
import com.xuan.common.core.enums.CodeRunStatus;
import com.xuan.judge.callback.DockerStartResultCallback;
import com.xuan.judge.callback.StatisticsCallback;
import com.xuan.judge.config.DockerSandBoxPool;
import com.xuan.judge.domain.CompileResult;
import com.xuan.judge.domain.SandBoxExecResult;
import com.xuan.judge.service.SandBoxPoolService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
public class SandBoxPoolServiceImpl implements SandBoxPoolService {


    @Autowired
    private DockerSandBoxPool dockerSandBoxPool;

    @Autowired
    private DockerClient dockerClient;

    @Value("${sandbox.limit.time:5}")
    private Long maxLimitTime;

    private String containerId;

    private String userCodeFileName;



    @Override
    public SandBoxExecResult execJavaCode(Long userId, String execCode, List<String> inputList) {
        // 1. 获取可用的 sandbox 容器
        containerId = dockerSandBoxPool.getContainer();
        // 2. 将要执行的代码存放到本地文件,以便后续将文件挂载到 docker 容器中
        createUserCodeFile(userId, execCode, containerId);
        // 3. 编译
        CompileResult compileResult = compileCodeByDocker();
        if (!compileResult.getCompile()) {
            // 在返回结果前删除文件并归还容器
            deleteCodeFile();
            dockerSandBoxPool.returnContainer(containerId);
            return SandBoxExecResult.fail(CodeRunStatus.COMPILE_FAIL, compileResult.getMessage());
        }
        // 4. 执行代码
        return execJavaCodeByDocker(inputList);
    }


    /**
     *  执行创建 CMD 指令的操作,并返回该指令的 id
     * @param inputArgs     题目方法的参数, 多个参数之间用 ' ' 隔开
     * @param javaCmdArr    java 操作基本指令 + 输入参数
     * @param containerId   容器id
     * @return              该执行指令的 id
     */
    private String execCreateCmd(String inputArgs, String[] javaCmdArr, String containerId) {
        if (StringUtils.isNotBlank(inputArgs)) {
            String[] inputArr = inputArgs.split(" ");   // 方法入参
            javaCmdArr = ArrayUtil.append(javaCmdArr, inputArr);
        }
        ExecCreateCmdResponse cmdResponse = dockerClient.execCreateCmd(containerId)
                .withCmd(javaCmdArr)
                .withAttachStdout(true)
                .withAttachStderr(true)
                // 这里有大坑
                .exec();
        return cmdResponse.getId();
    }

    /**
     *  创建本地文件,存放用户要执行的代码
     * @param userId
     * @param userCode
     */
    private void createUserCodeFile(Long userId, String userCode, String containerId) {
        // 判断二级目录(每个容器对应的目录)是否存在，不存在则创建
        String codeDir = dockerSandBoxPool.getContainerDir(containerId);
        userCodeFileName = codeDir + File.separator + JudgeConstants.EXEC_JAVA_CLASS_NAME;
        // 存在之前用户创建的文件则删除
        if (FileUtil.exist(userCodeFileName)) {
            FileUtil.del(userCodeFileName);
        }
        FileUtil.writeString(userCode, userCodeFileName, Constants.UTF8);
    }



    /**
     *  编译代码
     * @return
     */
    private CompileResult compileCodeByDocker() {
        // 获取 docker 编译指令id
        String cmdId = execCreateCmd(null, JudgeConstants.DOCKER_JAVA_FILE_COMPILE_CMD, containerId);
        DockerStartResultCallback resultCallBack  = new DockerStartResultCallback();
        CompileResult compileResult = new CompileResult();
        try {
            dockerClient.execStartCmd(cmdId).
                    exec(resultCallBack)
                    .awaitCompletion();
            if (CodeRunStatus.FAILED.equals(resultCallBack.getCodeRunStatus())) {
                compileResult.setCompile(false);
                compileResult.setMessage(resultCallBack.getErrorMessage());
            } else {
                compileResult.setCompile(true);
            }
            return compileResult;
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     *  执行编辑后的 Java 代码
     * @param inputList
     * @return
     */
    private SandBoxExecResult execJavaCodeByDocker(List<String> inputList) {
        List<String> execOutputList = new ArrayList<>();        // 记录代码的执行结果
        long maxMemory = 0;                                     // 记录所有输入示例中最大的运行内存
        long maxTime = 0;                                       // 记录所有输入示例中最大的运行时间
        // 执行每个测试用例
        for (String input : inputList) {
            String cmdId = execCreateCmd(input, JudgeConstants.DOCKER_EXEC_JAVA_CLASS_CMD, containerId);
            // 在代码开始执行前,开启监控
            StatisticsCallback statisticsCallback = new StatisticsCallback();
            StatsCmd statsCmd = dockerClient.statsCmd(containerId);
            statsCmd.exec(statisticsCallback);      // statisticsCallback 是回调对象
            // 在代码执行前开始计时
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();

            // 开始执行代码并限定最大运行时间,防止用户写出死循环,导致线程不能正常终止
            DockerStartResultCallback resultCallback = new DockerStartResultCallback();
            try {
                dockerClient.execStartCmd(cmdId)
                        .exec(resultCallback)
                        .awaitCompletion(maxLimitTime, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            stopWatch.stop();       // 结束计时
            statsCmd.close();       // 结束docker容器执行统计
            if (CodeRunStatus.FAILED.equals(resultCallback.getCodeRunStatus())) {
                return SandBoxExecResult.fail(CodeRunStatus.NOT_ALL_PASSED, resultCallback.getErrorMessage());
            }
            // 统计最大运行时内存,时间,输出结果
            if (statisticsCallback.getMaxUseMemory() != null) {
                maxMemory = Math.max(maxMemory, statisticsCallback.getMaxUseMemory());
            }
            maxTime = Math.max(maxTime, stopWatch.getTotalTimeMillis());
            execOutputList.add(resultCallback.getMessage());
        }
        // 规划容器
        // 删除用户代码对应的文件
        dockerSandBoxPool.returnContainer(containerId);
        deleteCodeFile();
        return SandBoxExecResult.success(execOutputList, maxTime, maxMemory);
    }


    private void deleteCodeFile() {
        FileUtil.del(userCodeFileName);
    }

}
